from django.core.urlresolvers import get_callable
from django.core.exceptions import ValidationError
from django.db.models.fields import TextField
from django.db.models.query import Q
from django.utils.encoding import smart_str
from django.utils.functional import memoize
from django.utils.translation import ugettext_lazy as _
from pyparsing import ParseBaseException
from parser import QueryFilterParser
import forms


_cache = {} # Maps callables to valid field names

def _get_valid_field_names(valid_field_names_func):
    if not callable(valid_field_names_func): # Maybe it's a string version
            valid_field_names_func = get_callable(valid_field_names_func)
    return valid_field_names_func()
_get_valid_field_names = memoize(_get_valid_field_names, _cache, 1)


class QueryFilterField(TextField):
    def __init__(self, verbose_name=None, name=None, \
                 valid_field_names_func=None, **kwargs):
        # TODO: Fix docstring
        """
        The 'valid_field_names_func' parameter is either a string like
        'project.student.util.filter_by_fields' which represents the path to a
        module and a function name, or a callable object. 
        The rest of the parameters have the same meaning as for
        TextField.__init__().
        """
        self.valid_field_names = _get_valid_field_names(valid_field_names_func)
        assert self.valid_field_names
        kwargs['blank'] = True
        kwargs['null'] = True
        TextField.__init__(self, verbose_name, name, **kwargs)  
            
    def _get_parser(self):
        if not hasattr(self, "_parser"):
            self._parser = QueryFilterParser(self.valid_field_names)
        return self._parser
    parser = property(_get_parser) 
    
    def contribute_to_class(self, cls, name):
        def as_Q_object(model_instance):
            return self.parser(getattr(model_instance, self.attname, None))

        super(QueryFilterField, self).contribute_to_class(cls, name)
        setattr(cls, "get_%s_as_Q_object" % self.name, as_Q_object)
        
    def formfield(self, **kwargs):
        defaults = {
            'form_class': forms.QueryFilterField,
            'parser_instance': self.parser
        }
        defaults.update(kwargs)
        return super(QueryFilterField, self).formfield(**defaults)

    def get_db_prep_lookup(self, lookup_type, value): # TODO: Implement
        raise TypeError('Lookup type %r not supported.' % lookup_type)

    def get_db_prep_value(self, value):
        """
        Tries to parse value into a django.db.models.query.Q object, raising
        django.core.exceptions.ValidationError if the data can't be parsed.
        Returns the original value.
        """
        try:
            self.parser(value)
            return value
        except ParseBaseException, err:
            raise ValidationError(
                forms.QueryFilterField.default_error_messages['invalid'] % err
            )
